package commons.base.netty.http;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import commons.base.netty.ChannelIdleTimeoutListener;
import commons.base.netty.IdleTimeoutConfig;
import commons.base.netty.IdleTimeoutHandler;
import commons.base.util.NamedThreadFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * 用netty实现的http服务器
 * 
 * @author zhe.deng<zhe.deng@happyelements.com>
 * @date 2015年2月7日 下午3:16:52
 *
 */
public class HttpServer {
	
	private static final Logger log = LogManager.getLogger(HttpServer.class);
	
	/**
	 * 主机
	 */
	private String host;
	/**
	 * 端口
	 */
	private int port;
	/**
	 * 启动器
	 */
	private ServerBootstrap bootstrap;

	/**
	 * ChannelHandler处理器列表
	 */
	private List<ChannelHandler> channelHandlerList = null;
	/** URI路由器 */
	private HttpRouter httpRouter = new HttpRouter();
	/** 业务线程池的线程数 */
	private int nThreads;
	/** 业务线程池 */
	private ExecutorService executorService;
	/**
	 * 空闲超时配置
	 */
	private IdleTimeoutConfig idleTimeoutConfig;
	private SslContext sslCtx;
	private ChannelInvalidPolicy channelInvalidPolicy = ChannelInvalidPolicy.RUN;
	
	public HttpServer(int port) {
		this.port = port;
		this.channelHandlerList = new ArrayList<ChannelHandler>();
		this.idleTimeoutConfig = new IdleTimeoutConfig(new ChannelIdleCloser());
		idleTimeoutConfig.setIdleTimeoutOp(true);
		idleTimeoutConfig.setReaderIdleTimeSeconds(30);
	}
	
	public HttpServer(String host, int port) {
		this.host = host;
		this.port = port;
		this.channelHandlerList = new ArrayList<ChannelHandler>();
	}
	
	/**
	 * 设置nThreads
	 * @param nThreads the nThreads to set
	 */
	public void setnThreads(int nThreads) {
		this.nThreads = nThreads;
	}
	
	public void addServlet(String path, NettyHttpServlet servlet){
		httpRouter.addServlet(path, servlet);
	}
	
	public void registServlet(WebRegister webRegister){
		webRegister.regist(httpRouter);
	}

	/**
	 * 添加处理器
	 * 
	 * @param name
	 */
	public void addChannelHandler(ChannelHandler channelHandler) {
		this.channelHandlerList.add(channelHandler);
	}
	
	public IdleTimeoutConfig getIdleTimeoutConfig() {
		return idleTimeoutConfig;
	}
	
	public void setSslCtx(SslContext sslCtx) {
		this.sslCtx = sslCtx;
	}

	public void setChannelInvalidPolicy(ChannelInvalidPolicy channelInvalidPolicy) {
		this.channelInvalidPolicy = channelInvalidPolicy;
	}

	/**
	 * 启动http服务器
	 */
	public void start() {
		if(channelHandlerList.isEmpty()){
			try {
				httpRouter.init();
			} catch (Exception e) {
				log.error(e.getMessage(), e);
			}
			if(nThreads > 0){
				executorService = Executors.newFixedThreadPool(nThreads, new NamedThreadFactory("netty-httpserver-executor"));
			}else{
				executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("netty-httpserver-executor"));
			}
		}
		
		bootstrap = new ServerBootstrap();
		
		bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
				.channel(NioServerSocketChannel.class)
				.option(ChannelOption.SO_REUSEADDR, true)
				.childOption(ChannelOption.TCP_NODELAY, true)
//				.handler(new LoggingHandler()) // debug
				.childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) throws Exception {
						ChannelPipeline pipeline = ch.pipeline();
						
						if (sslCtx != null) {
				            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
				        }
						
						if ((idleTimeoutConfig != null) && idleTimeoutConfig.isIdleTimeoutOp()) {
							pipeline.addLast(
									"idleStateHandler",
									new IdleStateHandler(idleTimeoutConfig.getReaderIdleTimeSeconds(),
											idleTimeoutConfig.getWriterIdleTimeSeconds(), idleTimeoutConfig
													.getAllIdleTimeSeconds(), TimeUnit.SECONDS));
							pipeline.addLast("idleHandler", new IdleTimeoutHandler(idleTimeoutConfig));
						}
						
						pipeline.addLast("codec", new HttpServerCodec());
//						pipeline.addLast(new ChunkedWriteHandler()); // upload file
						pipeline.addLast(new HttpObjectAggregator(16*1024*1024));
						pipeline.addLast(new HttpContentCompressor(1));
						pipeline.addLast(new WebSocketServerCompressionHandler());
						pipeline.addLast(new WebSocketServerProtocolHandler("/websocket", null, true));
						
						if(!channelHandlerList.isEmpty()){
							pipeline.addLast(channelHandlerList.toArray(new ChannelHandler[channelHandlerList.size()]));
						}else{
							pipeline.addLast("logic", new DefaultHttpHandler(httpRouter, executorService, channelInvalidPolicy));
							pipeline.addLast(new WebSocketFrameHandler());
						}
					}
				});
		
		// 绑定端口
		try {
			if (host != null) {
				bootstrap.bind(host, port).sync();
			} else {
				bootstrap.bind(port).sync();
			}
		} catch (InterruptedException e) {
			log.error(e.getMessage(), e);
			
			// Shut down the event loop to terminate all threads.
			bootstrap.config().group().shutdownGracefully();
			bootstrap.config().childGroup().shutdownGracefully();
		}
	}
	
	/**
	 * 关闭服务器
	 */
	public void shutdown() {
		httpRouter.shutdown();
		if(executorService != null){
			executorService.shutdown();
		}
		if (bootstrap != null) {
			bootstrap.config().group().shutdownGracefully();
			bootstrap.config().childGroup().shutdownGracefully();
		}
	}

}

class ChannelIdleCloser implements ChannelIdleTimeoutListener {
	
	private static final Logger logger = LogManager.getLogger(ChannelIdleCloser.class);
	
	@Override
	public void allIdleTimeout(ChannelHandlerContext ctx) {
		logger.warn("all idle timeout. close channel : {}", ctx.channel());
		ctx.channel().close();
	}

	@Override
	public void readIdleTimeout(ChannelHandlerContext ctx) {
		logger.warn("read idle timeout. close channel : {}", ctx.channel());
		ctx.channel().close();
	}

	@Override
	public void writeIdleTimeout(ChannelHandlerContext ctx) {
		logger.warn("write idle timeout. close channel : {}", ctx.channel());
		ctx.channel().close();
	}
}
